package gazzera.node.core.messages;

import gazzera.utils.*;

import java.io.*;
import java.util.*;



public abstract class Message
{
	protected static Map<Integer, Class<? extends Message>> map1 = new HashMap<Integer, Class<? extends Message>>();
	protected static Map<Class<? extends Message>, Integer> map2 = new HashMap<Class<? extends Message>, Integer>();
	
	static
	{
		map1.put(1, Hello.class);
		map1.put(2, Ping.class);
		map1.put(3, Pong.class);
		map1.put(4, Chat.class);
		map1.put(5, ChunkSearch.class);
		map1.put(6, ChunksAvaiable.class);
		map1.put(7, Enqueue.class);
		map1.put(8, ReadyToSend.class);
		map1.put(9, StartToSend.class);
		map1.put(10, ChunkData.class);
		map1.put(11, NotInterested.class);
		map1.put(12, BrowseSearch.class);
		map1.put(13, BrowseReplyFile.class);
		map1.put(14, BrowseReplyFolder.class);
		map1.put(15, TrustingYou.class);
		map1.put(16, PersonTrustingMe.class);
		map1.put(17, BrowseReplyFinished.class);
		map1.put(18, FileHash.class);
		
		for (Integer i : map1.keySet())
		{
			Class<? extends Message> cls = map1.get(i);
			map2.put(cls, i);
		}
		
	}
	
	protected abstract void send(DataOutputStream s) throws IOException;
	
	protected abstract void recv(DataInputStream s) throws IOException;
	
	public Message()
	{
		//
	}
	
	protected final void sendString(DataOutputStream s, String x) throws IOException
	{
		if(x!=null)
		{
		    byte[] bytes = x.getBytes("UTF-8");
		    s.writeShort(bytes.length);
		    s.write(bytes);
		}
		else
		{
			s.writeShort(0);
		}
	}
	protected final void sendBuffer(DataOutputStream s, byte[] x) throws IOException
	{
		s.writeShort(x.length);
		s.write(x);
	}
	protected final void sendInt(DataOutputStream s, int x) throws IOException
	{
		s.writeInt(x);
	}	
	protected final void sendByte(DataOutputStream s, byte x) throws IOException
	{
		s.writeByte(x);
	}	
	protected final void sendBoolean(DataOutputStream s, boolean x) throws IOException
	{
		s.writeBoolean(x);
	}	
	protected final void sendLong(DataOutputStream s, long x) throws IOException
	{
		s.writeLong(x);
	}
	protected final void sendBitset(DataOutputStream s, BitSet x) throws IOException
	{
		byte[] b = toByteArray(x);
		sendBuffer(s, b);
	}
	protected final void sendHash(DataOutputStream s, Hash x) throws IOException
	{
		byte[] b = x.toByteArray();
		sendBuffer(s, b);
	}
	protected final void sendFileID(DataOutputStream s, FileID id) throws IOException
	{
		FileIDUtils.write(id,s);
	}
	protected final FileID readFileID(DataInputStream s) throws IOException
	{
	    return FileIDUtils.read(s);
	}
	protected final Hash readHash(DataInputStream s) throws IOException
	{
		byte[] b = readBuffer(s);
		return new Hash(b);
	}
	
	protected final BitSet readBitset(DataInputStream s) throws IOException
	{
		byte[] b = readBuffer(s);
		return fromByteArray(b);
	}
	

	protected final String readString(DataInputStream s) throws IOException
	{
		short z = s.readShort();
		byte[] b = new byte[z];
		s.readFully(b);
		return new String(b,"UTF-8");
	}
	protected final byte[] readBuffer(DataInputStream s) throws IOException
	{
		short z = s.readShort();
		byte[] b = new byte[z];
		s.readFully(b);
		return b;
	}
	protected final int readInt(DataInputStream s) throws IOException
	{	
		return s.readInt();
	}
	protected final byte readByte(DataInputStream s) throws IOException
	{	
		return s.readByte();
	}
	protected final boolean readBoolean(DataInputStream s) throws IOException
	{	
		return s.readBoolean();
	}
	protected final long readLong(DataInputStream s) throws IOException
	{	
		return s.readLong();
	}
	
	
	public void writeOk(DataOutputStream os) throws IOException
	{
		Integer i = map2.get(this.getClass());
		os.write(i);
		send(os);
	}
	
	public static Message readOk(DataInputStream os) throws IOException
	{
		int i = os.read();
		if (i==-1)
		{
			throw new IOException("EOF");
		}
		Class<? extends Message> c = map1.get(i);
		if(c==null)
		{
			throw new IOException("Unknown message type");
		}
		Message m;
		try
		{
			m = c.newInstance();
		}
		catch (Exception e)
		{
			throw new IOException("Message class doesn't have empty constructor?", e);
		}
		m.recv(os);
		return m;
	}
	
	// Returns a bitset containing the values in bytes. 
	// The byte-ordering of bytes must be big-endian which means the most significant bit is in element 0. 
	public static BitSet fromByteArray(byte[] bytes)
	{
		BitSet bits = new BitSet();
		for (int i = 0; i < bytes.length * 8; i++)
		{
			if ((bytes[bytes.length - i / 8 - 1] & (1 << (i % 8))) > 0)
			{
				bits.set(i);
			}
		}
		return bits;
	} 
	
	// Returns a byte array of at least length 1. 
	// The most significant bit in the result is guaranteed not to be a 1 
	// (since BitSet does not support sign extension). 
	// The byte-ordering of the result is big-endian which means the most significant bit is in element 0. 
	// The bit at index 0 of the bit set is assumed to be the least significant bit. 
	public static byte[] toByteArray(BitSet bits)
	{
		byte[] bytes = new byte[bits.length() / 8 + 1];
		for (int i = 0; i < bits.length(); i++)
		{
			if (bits.get(i))
			{
				bytes[bytes.length - i / 8 - 1] |= 1 << (i % 8);
			}
		}
		return bytes;
	} 
	
}
